package com.funnel;

import java.util.HashMap;
import java.util.Map;

/**
 * @author tiny
 * @date 2022/2/26 22:20
 * @Description: 漏斗限流代码思想
 */
public class FunnelRateLimiter {

    private Map<String,Funnel> funnels = new HashMap<>();

    static class Funnel{
        int capacity; //漏斗容量
        float leakingRate; //漏水速率 (毫秒单位时间漏水量)
        int leftQuota; //剩余可用容量
        long leakingTs; //上一次放水时间点

        public Funnel(int capacity,float leakigRate){
            this.capacity = capacity;
            this.leakingRate = leakigRate;
            this.leftQuota = capacity; //初始化时剩余可用容量为漏斗总容量
            this.leakingRate = System.currentTimeMillis();
        }

        void makeSpace(){
            long nowTs = System.currentTimeMillis();
            long deltaTs = nowTs - leakingTs; //漏水（请求间隔）时长
            int deltaQuota = (int)(deltaTs * leakingRate);
            if( deltaQuota > 9 ){ //间隔时间足够长,水漏光了
                this.leftQuota = capacity;
                this.leakingTs = nowTs;
                return;
            }
            if(deltaQuota < 3){
                return; //漏的时间不够长，可用空间接近最低阈值
            }
            this.leftQuota += deltaQuota; //加上漏掉的量就是当前的可用空间
            //注意可用空间不能大于容量
            this.leftQuota = this.leftQuota > this.capacity ? this.capacity : this.leftQuota;
            this.leakingTs = nowTs;
        }

        boolean watering(int quota){
            makeSpace();
            if( (this.leftQuota-=quota) > 0 ){
                return true;
            }
            return false;
        }
    }

    public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakigRate){
        String key = String.format("%s:%s",userId,actionKey);
        Funnel funnel = funnels.get(key);
        if(null == funnel){
            funnel = new Funnel(capacity,leakigRate);
            funnels.put(key,funnel);
        }
        return funnel.watering(3);
    }
}
